{--
    The following module exemplifies the _flexibly typed numeric literal_ (FTNL) typing rules.
    
    A numeric literal with a suffix (one of 'l', 'n', 'f', 'd', 'L', 'N', 'F', 'D' ) is
    never a FTNL. The type is fixed to 'Long', 'Integer', 'Double' or 'Float'.
    
    An integer FTNL is either @0@ or a non-null digit followed by arbitrary many digits, 
    without suffix letter. Octal literals (which are starting with @0@ and have at least one 
    further digit) and hexadecimal literals (starting with @0x@) are not FTNLs, rather,
    their type is invariably 'Int'. 
    
    A floating point FTNL is a number in decimal or scientific notation, without suffix letter. 
    
    -} 

module examples.NumericLiterals where

import Prelude.Math

{-- 
    1. If there is one and only one numeric type (that is, instance of type class 'Num')
       that is valid for the integer FTNL, 
       then the literal denotes a value of that type.

    Since @n@ is 'Integer', both literals must be 'Integer'
    -}
rule1 :: Integer -> Bool
{-- Alternatively, the following types would be possible:
    > rule1 :: Int -> Bool
    > rule1 :: Long -> Bool
    > rule1 :: Integral i => i -> Bool
    -} 
rule1 n = n `rem` 2 != 0

--- Since @x@ is 'Float', both literals must be 'Float' literals.
--- Alternatively, the following types would be possible:
---     > rule1d :: Double -> Double
---     > rule1d :: Real r => r -> r
rule1d :: Float -> Float
rule1d x = 0.5 * x + 1



{-- 
    2. If only a type that is an instance of 'Real' 
       or some class that has 'Real' as superclass would be valid, 
       it denotes a value of type 'Double'.
    
    Since 
    
    > sqrt :: Floating α => α -> α
    > (**) :: Floating α => α -> α -> α
    
    and 'Floating' is a subclass of 'Real', both literals will be 'Double'.
    -}
-- rule2 :: Float               -- also possible
-- rule2 :: Floating r => r     -- also possible (see below)
-- rule2 :: Double              -- default
rule2 = sqrt 2 ** 7

{--
    3. If a type signature is present which forces the type to be polymorphic, 
       the literal will be replaced by an application of 'fromInt' or 'fromDouble'
       to the same literal 
       (which then itself will get typed as 'Int' or 'Double' by the previous rules).
       
    Because of the type signature, the literals must be polymorphic, which is achieved
    through implicit application of 'fromInt' to @7@ and 'fromDouble' to @2.0@. 
    However, this requires that type variables
    that stand for the type of the literal must have a constraint that
    ensures they are numbers, i.e. instances of (a subclass of) 'Num'.

    -}
rule3a :: Floating r => r
rule3a =  pi * 2

rule3b :: Integral n => n -> Bool
rule3b  n = n `rem` 2 != 0

--      inferred type is more constrained than expected type 
--      inferred: Num a => a ->  a 
--      expected: a -> a
-- rule3c :: a -> a
-- rule3c  n =  n + 2

{--
    4. If the type of an integer literal cannot be determined by the rules above,
       it defaults to 'Int'. 
    
    The functions 'rem' and '!=' are polymorphic over 'Integral'/'Eq' types,
    so no concrete type is enforced by the code, and rules 2 and 3 above
    are not applicable. Therefore, it defaults to 'Int'.
-}
rule4  n = n `rem` 2 != 0

--- @2.0@ gets type 'Double' since the type of @x@ is unknown.
rule4d x = x / 2.0

{-- A FTNL cannot be used to resolve an overloaded native function,
    even if there would be at most one valid fit.
     
    Consider the following native definition, where the second new
    accepts a 'Long'
    --}
data D = native java.util.Date where
    native new :: ()   -> IOMutable D
                | Long -> STMutable s D


{-- Nevertheless, the following will not typecheck.

    > D.new 0

    The technical reason being that both overload resolution and FTNL resolution
    try to unify an unbound type variable with something.
    
    If literal resolution runs first, it will just plug in 'Int', 
    and this won't fit with any possible type signature of 'Date.new'.
    
    If, otoh, overload resolution runs first, both '()' and 'Long' are
    acceptable choices, and the resolution fails due to ambiguity.
    --}
-- epoch = D.new 0 -- overloaded new cannot be resolved at type Int->α
epoch = D.new 0L
